package com.qf.studentdemo.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.qf.studentdemo.mapper.StudentMapper;
import com.qf.studentdemo.pojo.Student;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/stu")
public class StudentController implements InitializingBean {

    //声明一个存储学生id的博隆过滤器
    private RBloomFilter bloomFilter;

    //在spring创建完StudentController对象后，执行初始化阶段时，会执行到这个方法
    @Override
    public void afterPropertiesSet() throws Exception {
        bloomFilter = client.getBloomFilter("stuIdsBl");

        //初始化一个位图数组（参数一：期望存入的id数量，参数二：允许误判率）
        bloomFilter.tryInit(1000, 0.01);

        //向这个位图数组中存入学生id
        List<Integer> ids = studentMapper.findIds();
        for (Integer id : ids) {
            bloomFilter.add(id);
        }
    }

    //引入Redisson依赖后，会自动向spring容器中注入这个的对象。通过这个对象可以获取布隆过滤器&分布式锁
    @Autowired
    private RedissonClient client;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private StudentMapper studentMapper;

    private String STU_CACHE_PREFIX = "CACHE:STUDENT:ID:";
    private String STU_LOCk_PREFIX = "LOCK:STUDENT:ID:";

    @GetMapping("/{id}")
    public Student getOne(@PathVariable("id") Integer id) throws InterruptedException {
        String key = STU_CACHE_PREFIX + id;

        //利用布隆过滤器解决缓存穿透问题。判断布隆过滤器中是否有指定的id
        if (bloomFilter.contains(id)) {

            //休眠，等待缓存过期
            Thread.sleep(5000);

            // 数据库可能存在
            //1、先查询缓存
            String stuJson = redisTemplate.opsForValue().get(key);

            //2、判断缓存
            if (StringUtils.isBlank(stuJson)) {
                //2.1、缓存中没有
                String lockName = STU_LOCk_PREFIX + id;
                RLock lock = client.getLock(lockName);
                Student stu = null;
                try {
                    //查询数据库时，加上一个分布式锁，解决缓存击穿问题
                    lock.lock();

                    //再次判断缓存中是否有数据（双重校验-DC）
                    stuJson = redisTemplate.opsForValue().get(key);
                    if (StringUtils.isBlank(stuJson)) {
                        System.out.println("查询数据库");
                        stu = studentMapper.selectById(id);
                        if (stu != null) {
                            //数据库查到了数据,存入缓存 (设置缓存的过期时间，演示缓存击穿问题)
                            redisTemplate.opsForValue().set(key, JSON.toJSONString(stu),3, TimeUnit.SECONDS);
                        }
                    }else{
                        System.out.println("缓存命中222....");
                        stu = JSON.parseObject(stuJson, Student.class);
                    }
                }finally {
                    lock.unlock();
                }
                return stu;
            } else {
                //2.2、缓存中有
                System.out.println("命中缓存");
                Student stu = JSON.parseObject(stuJson, Student.class);

                return stu;
            }
        }else{
            System.out.println("数据库没有这个数据");
            return null;
        }
    }
}
